unit Unit3;

interface

procedure RevisePaymentsAndCheques ( span: integer; quick: boolean );

implementation

uses
   System.SysUtils, System.DateUtils, System.Types, System.Classes, System.Generics.Collections, System.Generics.Defaults, System.Math, Unit4;

type

   TPaymentType = ( CASH , BANKCARD );

   TReqType = ( ofd, espp, createPayment, bill, billError, cheque, payTransaction );

   TPaperValue = ( p10, p50, p100, p200, p500, p1000, p2000, p5000 );

   TPapers = array [ TPaperValue ] of byte;

   TPaperAmounts = array [ TPaperValue ] of Int64; {купюры в комейках}

   {Транзакции терминала}
   TTerminalTransaction = record
papers : TPapers;     {               купюроприёмник            payTransaction  callback           }
actId,                {createPayment                  POS       payTransaction  callback       ЕСПП}
trId,                 {createPayment                                                           ЕСПП}
svcNum,               {                               POS       payTransaction                 ЕСПП}
fpd,                  {                                    чек  payTransaction  callback  ОФД      }
log : string;         {createPayment  купюроприёмник  POS  чек  payTransaction  callback  ОФД      }
dt : TDateTime;       {createPayment  купюроприёмник  POS  чек  payTransaction  callback  ОФД  ЕСПП}
serialNumber,         {                                    чек  payTransaction  callback           }
throughNumber,        {                                    чек  payTransaction  callback           }
amount : Integer;     {               купюроприёмник  POS       payTransaction  callback  ОФД  ЕСПП}
ptCode: TPaymentType; {createPayment  купюроприёмник  POS       payTransaction  callback  ОФД  ЕСПП}
ReqType: TReqType;
ReqStatus: Boolean;
   end;

   {Платежи терминала}
   TTerminalPayment = array [ TReqType ] of TTerminalTransaction;

   TBillParser = class
      paper: TPaperValue; { купюра задана, если ReqType = bill   }
      dt: TDateTime;
      ReqType: TReqType;
      ReqStatus: Boolean; { запись пуста, если ReqStatus = False }
      log: string;
      function parseBills ( s: string ): integer;
   end;

const
   PaperAmounts : TPaperAmounts = ( 1000 , 5000 , 10000 , 20000 , 50000 , 100000 , 200000 , 500000 );

var
   f: text;
   OneTransaction: TTerminalTransaction;             {Одна транзакция терминала}
   OnePayment: TTerminalPayment; {Один платёж терминала}
   wordie: TArray<String>;
   PaymentTypeName: array [ TPaymentType ] of string;
{   ReqTypeName: array [ TReqType ] of string;}
   TransactionsTimeComparer: IComparer<TTerminalTransaction>;
   TransactionsActIdComparer: IComparer<TTerminalTransaction>;
   TransactionsTrIdComparer: IComparer<TTerminalTransaction>;
   TransactionsThroughNumberComparer: IComparer<TTerminalTransaction>;
   PaymentTimeComparer: IComparer<TTerminalPayment>;
   s, reviseString: string;
   u: RawByteString;
{   OneReqType: TReqType;}
   bp: TBillParser;
   Revise: array [ 1 .. 3 ] of TStringList;{
                   1 вывод списков транзакций и платежей в файл, используется в процедурах *toLog
                      2 список ошибок разбора
                        3 вывод сверки в текстовый файлик }
   ActionTransactions: TList <TTerminalTransaction>; {ЕСПП}
   FiscalTransactions: TList <TTerminalTransaction>; {ОФД}
   ExcludedFiscalTransactions: TList <TTerminalTransaction>; {ОФД - список чеков исключаемых из сверки. Их сумма по налу и безналу должна быть равна нулю}
   BillTransactions  : TList <TTerminalTransaction>; {Терминал}
   BillTransactions2 : TList <TTerminalTransaction>; {Терминал, второй проход}
   GeneralPayments,
   SuccessfulPayments,
   DoublePayments
                 : TList <TTerminalPayment>;         {Платёж}

function TBillParser.parseBills ( s: string ): integer;
var
   wordie: TArray<String>;
   amount, j: integer;
   p: TPaperValue;

   procedure AddStatus ( k: integer );
   var
      w: string;
      i: integer;
   begin
      w := wordie [ k ];
      if  ( w.Length > 3 )
      and ( w <> 'UnitDisabled' )
      and ( w <> 'PowerUp' )
      then begin

         for i := k + 1 to Length ( wordie ) - 1 do
            w := w + ' ' + wordie [ i ];

         bp.dt := EncodeDateTime (
            wordie [ 0 ].Substring( 0, 4).ToInteger,
            wordie [ 0 ].Substring( 5, 2).ToInteger,
            wordie [ 0 ].Substring( 8, 2).ToInteger,
            wordie [ 1 ].ToInteger,
            wordie [ 2 ].ToInteger,
            wordie [ 3 ].Substring( 0, 2).ToInteger,
            wordie [ 3 ].Substring( 3, 3).ToInteger);

         bp.ReqType := billError;
         bp.ReqStatus := True;
         bp.log :=
            wordie [ 0 ].Substring( 8, 2) + '.' +
            wordie [ 0 ].Substring( 5, 2) + '.' +
            wordie [ 0 ].Substring( 0, 4) + ' ' +
            wordie [ 1 ] + ':' +
            wordie [ 2 ] + ':' +
            wordie [ 3 ] + '   ' +
            w;

      end;
   end;

begin
   amount := 0;
   bp.ReqStatus := False;
   wordie := s.Split( [' ','"',':','(',')','/','\','{','}'] , TStringSplitOptions.ExcludeEmpty );

   for j := 0 to Length ( wordie ) - 1 do
      if wordie [ j ] = 'cbEscrow'
      then begin
         amount := 0;
         try
            wordie [ j + 2 ].ToInteger;
         except
            Break;
         end;

         bp.dt := EncodeDateTime (
            wordie [ 0 ].Substring( 0, 4).ToInteger,
            wordie [ 0 ].Substring( 5, 2).ToInteger,
            wordie [ 0 ].Substring( 8, 2).ToInteger,
            wordie [ 1 ].ToInteger,
            wordie [ 2 ].ToInteger,
            wordie [ 3 ].Substring( 0, 2).ToInteger,
            wordie [ 3 ].Substring( 3, 3).ToInteger);

         bp.ReqType := billError;
         bp.ReqStatus := True;
         bp.log :=
            wordie [ 0 ].Substring( 8, 2) + '.' +
            wordie [ 0 ].Substring( 5, 2) + '.' +
            wordie [ 0 ].Substring( 0, 4) + ' ' +
            wordie [ 1 ] + ':' +
            wordie [ 2 ] + ':' +
            wordie [ 3 ] + '   ' +
            'Escrow ' + wordie [ j + 2 ];
         break;
      end
      else
         if wordie [ j ] = 'cbStacked'
         then begin
            try
               amount := wordie [ j + 2 ].ToInteger;
            except
               amount := 0;
               Break;
            end;
            for p := Low ( TPaperValue ) to High ( TPaperValue ) do
               if PaperAmounts [ p ] = amount * 100
               then bp.paper := p;

            bp.dt := EncodeDateTime (
               wordie [ 0 ].Substring( 0, 4).ToInteger,
               wordie [ 0 ].Substring( 5, 2).ToInteger,
               wordie [ 0 ].Substring( 8, 2).ToInteger,
               wordie [ 1 ].ToInteger,
               wordie [ 2 ].ToInteger,
               wordie [ 3 ].Substring( 0, 2).ToInteger,
               wordie [ 3 ].Substring( 3, 3).ToInteger);

            bp.ReqType := bill;
            bp.ReqStatus := True;
            bp.log :=
               wordie [ 0 ].Substring( 8, 2) + '.' +
               wordie [ 0 ].Substring( 5, 2) + '.' +
               wordie [ 0 ].Substring( 0, 4) + ' ' +
               wordie [ 1 ] + ':' +
               wordie [ 2 ] + ':' +
               wordie [ 3 ] + '   ' +
               'Stacked ' + wordie [ j + 2 ];
            break;
         end
         else
            if wordie [ j ] = 'event'
            then begin
               try
                  AddStatus ( j + 3 );
               except
                  try
                     AddStatus ( j + 1 );
                  except
                     Break;
                  end;
               end;
               Break;
            end;
   parseBills := amount;
end;

function DotToComma ( w: string ): string; begin if Pos ( '.' , w ) > 0 then DotToComma := w.Substring( 0 , Pos ( '.' , w ) - 1 ) + ',' + w.Substring( Pos ( '.' , w ) , Length ( w ) - Pos ( '.' , w ) ) else DotToComma := w; end;

procedure clearTerminalTransaction ( var a : TTerminalTransaction );
var OnePaperValue: TPaperValue;
begin
   with a do  begin
      actId := '';
      trId := '';
      svcNum := '';
      fpd := '';
      log := '';
      dt := 0;
      serialNumber := -1;
      throughNumber := -1;
      amount := 0;
      ReqStatus := False;
      for OnePaperValue := Low ( TPaperValue ) to High ( TPaperValue ) do
         Papers [ OnePaperValue ] := 0;

   end;
end;

procedure parseOFDtextString;
begin
   with OneTransaction do
      try{}
         wordie := ofdString.Split([';'],TStringSplitOptions.ExcludeEmpty);
         if ( wordie [ 4 ].Contains('нал') )
         or ( wordie [ 4 ].Contains('эле') )
         then begin
            clearTerminalTransaction( OneTransaction );
            log := ofdString;
            dt := strToDateTime ( wordie [ 1 ] );
            fpd := wordie [ 2 ];
            amount := Round ( wordie [ 6 ].ToSingle * 100 ); {электронные}
            ReqType := ofd;
            ReqStatus := True;
            if amount = 0
            then begin
               amount := Round ( wordie [ 5 ].ToSingle * 100 ); {наличные}
               ptCode := CASH;
               if amount <> 0
               then FiscalTransactions.Add( OneTransaction );
            end
            else begin
               ptCode := BANKCARD;
               FiscalTransactions.Add( OneTransaction );
            end;
         end;
      except
         ReqStatus := False;
         Revise [ 2 ].Add( 'Ошибка в выгрузке из ОФД: ' + log );
      end;{try}
end;

procedure parseOFDapiString;
var
   i: integer;
begin
   wordie := ofdString.Split(['[',']','{','}',',','"',':',' '],TStringSplitOptions.ExcludeEmpty);
   for i := 0 to Length ( wordie ) - 28 do
      if ( wordie [ i ] = 'id' ) and ( wordie [ i + 9 ] = 'TICKET' )
      then
         with OneTransaction do  begin
            clearTerminalTransaction( OneTransaction );
            log :=
               wordie [ i +  5 ] + ':'
             + wordie [ i +  6 ] + ':'
             + wordie [ i +  7 ] + ';'
             + wordie [ i + 11 ] + ';'{ transaction.ticket.sell                transaction.ticket.sell.return       }
             + wordie [ i + 12 ] + ';'{ transaction.ticket.payment.type.cash   transaction.ticket.payment.type.card }
             + wordie [ i + 16 ] + ';'{ ФПД }
             + wordie [ i + 23 ] + ';'{ сумма наличными }
             + wordie [ i + 27 ];     { сумма по карте  }
            if wordie [ i + 12 ].Contains('cash')
            then ptCode := CASH
            else
               if wordie [ i + 12 ].Contains('card')
               then ptCode := BANKCARD
               else Continue;
            fpd := wordie [ i + 16 ];
            try
               dt := strToDateTime (
                  wordie [ i +  5 ].Substring (  8 , 2 ) + '.' {день}
                + wordie [ i +  5 ].Substring (  5 , 2 ) + '.' {месяц}
                + wordie [ i +  5 ].Substring (  0 , 4 ) + ' ' {год}
                + wordie [ i +  5 ].Substring ( 11 , 2 ) + ':' {час}
                + wordie [ i +  6 ] + ':'                      {минута}
                + wordie [ i +  7 ].Substring (  0 , 2 ) + ',' {секунда}
                + wordie [ i +  7 ].Substring (  3 , 3 )       {миллисекунда}
               );
               try
                  amount := Round ( DotToComma ( wordie [ i + 23 ] ).ToSingle * 100 ) + Round ( DotToComma ( wordie [ i + 27 ] ).ToSingle * 100 );
               except
                  amount := Round ( DotToComma ( wordie [ i + 21 ] ).ToSingle * 100 ) + Round ( DotToComma ( wordie [ i + 25 ] ).ToSingle * 100 );
               end;
            except
               ReqStatus := False;
               Revise [ 2 ].Add( 'Ошибка в выгрузке из ОФД: ' + log );
            end;{try}
            ReqType := ofd;
            ReqStatus := amount <> 0;
            if ReqStatus
            then FiscalTransactions.Add( OneTransaction );
            if wordie [ i + 11 ].Contains('return')
            then amount := -amount;
         end;{with oneTransaction}
end;

function parseOFD ( j: integer;   fn: string ): integer;
begin
   assignFile ( f , fn );
   reset ( f );
   while not Eof ( f ) do  begin
      readln ( f , ofdString );
      if ofdString.Length > 200 then parseOFDapiString else parseOFDtextString;
   end;{while not eof}
   closeFile ( f );
   parseOFD := j + 1;
end;

procedure writeTransactionToLog ( var a: TTerminalTransaction; l: byte );
{записывает транзакцию A в лог номер L }
var
   s: string;
   OnePaperValue: TPaperValue;
   foundNearby: Boolean;
begin
   with a do  begin
      s := '';
      if ReqStatus or ( ReqType = espp ) {Печатаем или нет}
      then begin
         case ReqType of
            createPayment: s := s + 'Запрос createPayment ';
            bill: s := s + 'Лог оплаты ';
            cheque:
               if amount < 0
               then s := s + 'Чек возврата на ККМ'
               else s := s + 'Чек продажи на ККМ ';
            payTransaction: s := s + 'Запрос payTransaction ';
            espp:
               if ReqStatus
               then
                  if amount < 0
                  then s := s + 'Возврат платежа '
                  else
                     if amount > 0
                     then s := s + 'Платёж '
                     else
               else
                  if ( actId <> '' ) and ( trId <> '' ) and ( svcNum <> '' )
                  then s := s + 'Новый платёж ';
            ofd:
               if amount < 0
               then s := s + 'Чек возврата в ОФД '
               else
                  if amount > 0
                  then s := s + 'Чек прихода в ОФД ';
         end;
         case ReqType of
            createPayment, espp:
               if trId <> ''
               then s := s + ' '+trId+' ';
         end;
         if ( ReqStatus ) and ( ReqType <> createPayment ) and ( ReqType <> cheque )
         then s := s + PaymentTypeName [ ptCode ];
         if ReqStatus and ( dt > 0 )
         then s := s + ' в '+ DateTimeToStr( dt )+' ';
         if amount <> 0
         then
            case ReqType of
               bill, payTransaction, espp, ofd:
                  s := s + ' на сумму '
                         + FormatFloat ( '#########.00' , amount / 100 )
                         +' рублей ';
            end;
         if svcNum <> ''
         then
            case ReqType of payTransaction, espp:
               s := s + 'на реквизит '+svcNum;
            end;
         if actId <> ''
         then
            case ReqType of
               createPayment, payTransaction, espp:
                  s := s + '   идентификатор чека '+actId+'   ';
            end;
         if serialNumber > 0
            then
               case ReqType of cheque, payTransaction:
                  s := s + ' номер чека в смене '+serialNumber.ToString+'   ';
               end;
         if throughNumber > 0
         then
            case ReqType of
               cheque, payTransaction:
                  s := s + ' сквозной номер чека '+throughNumber.ToString+'   ';
               end;
         if fpd <> ''
         then s := s+' ФПД '+fpd+'   ';
         Revise [ l ].Add ( s );

         foundNearby := False;
         if ptCode = CASH
         then
            for OnePaperValue := Low ( TPaperValue ) to High ( TPaperValue ) do
               foundNearby := foundNearby or ( Papers [ OnePaperValue ] > 0 );

         if foundNearby
         then begin
            s := 'Купюры:';
            for OnePaperValue := Low ( TPaperValue ) to High ( TPaperValue ) do
               if Papers [ OnePaperValue ] > 0
               then
                  s :=
                     s + '   '
                       + Papers [ OnePaperValue ].ToString
                       + ' x '
                       + Round ( PaperAmounts [ OnePaperValue ] / 100 ).ToString;
            Revise [ l ].Add ( s );
         end;

         if log <> ''
         then
            case ReqType of
               createPayment, bill, cheque, payTransaction, ofd:
                  begin
                     Revise [ l ].Add( 'Логи: ' + log );
                  end;
            end;
         Revise [ l ].Add('');
      end;
   end;
end;

procedure clearPayment ( var a : TTerminalPayment );
var ReqType: TReqType;
begin
   for ReqType := Low ( TReqType ) to High ( TReqType ) do  clearTerminalTransaction( a [ ReqType ] );
end;

procedure writeGeneralPaymentToFile ( i : Integer;  l : byte );
var ReqType: TReqType;
begin
   for ReqType := Low ( TReqType ) to High ( TReqType ) do
      begin
         OneTransaction := GeneralPayments [ i ][ ReqType ];
         writeTransactionToLog ( OneTransaction , l );
      end;
   Revise [ l ].Add('');
   Revise [ l ].Add('');
   Revise [ l ].Add('');
end;

procedure writeDoublePaymentToFile ( i : Integer; l : byte );
var ReqType: TReqType;
begin
   for ReqType := Low ( TReqType ) to High ( TReqType ) do
      begin
         OneTransaction := DoublePayments [ i ][ ReqType ];
         writeTransactionToLog ( OneTransaction , l );
      end;
   Revise [ l ].Add('');
   Revise [ l ].Add('');
   Revise [ l ].Add('');
end;

procedure writeSuccessfulPaymentToFile ( i : Integer; l : byte );
var ReqType: TReqType;
begin
   for ReqType := Low ( TReqType ) to High ( TReqType ) do
      begin
         OneTransaction := SuccessfulPayments [ i ][ ReqType ];
         writeTransactionToLog ( OneTransaction , l );
      end;
   Revise [ l ].Add('');
   Revise [ l ].Add('');
   Revise [ l ].Add('');
end;

procedure RevisePaymentsAndCheques ( span: integer ; quick: boolean );
var
   oneAmount, i, j, k, l, m, n, o, p, q, i0, i1: integer;
   OnePaperValue: TPaperValue;
   foundNearby: Boolean;
   onePaymentType: TPaymentType;
   dtOne, dt1, dt2: TDateTime;
begin
   try
   { TODO -oUser -cConsole Main : Insert code here }
   WriteToGlobalLog ('Начата сверка чеков и оплат');

   { ---===<*> Очищаем списки <*>===--- }
   Revise [ 1 ].Clear;
   Revise [ 2 ].Clear;
   Revise [ 3 ].Clear;
   ActionTransactions.Clear;
   FiscalTransactions.Clear;
   ExcludedFiscalTransactions.Clear;
   BillTransactions.Clear;
   BillTransactions2.Clear;
   GeneralPayments.Clear;
   SuccessfulPayments.Clear;
   DoublePayments.Clear;

   { ---===<*> Загружаем T_ACTION <*>===--- }
   if FileExists ('action.csv')
   then Revise [ 1 ].LoadFromFile('action.csv')
   else begin
      Revise [ 3 ].Add('action.csv не найден');
      if Revise [ 3 ].Count > 0 then Revise [ 3 ].SaveToFile('revise.txt');
      Exit;
   end;
   for i := 0 to Revise [ 1 ].Count - 1 do  begin
      wordie := Revise [ 1 ] [ i ].Split(['"',';'],TStringSplitOptions.ExcludeEmpty);
      if Length ( wordie ) > 6 then begin {пропускаем начала-концы смен}
         clearTerminalTransaction( OneTransaction );
         with OneTransaction do  begin

            reqType := espp;
            svcNum := wordie [ 0 ];
            actId := wordie [ 1 ];
            trId := wordie [ 2 ];

            try {если заполнены все поля}
               dt := strToDateTime ( wordie [ 6 ] );
               amount := Round( wordie [ 7 ].ToSingle );
               case Round ( wordie [ 4 ].ToSingle ) of
                  1: ptCode := BANKCARD;
                  7: ptCode := CASH;
               end;
            except

               try {у новых платежей не заполнен способ оплаты}
                  dt := strToDateTime ( wordie [ 5 ] );
                  amount := Round( wordie [ 6 ].ToSingle );
               except
                  Revise [ 2 ].Add( Revise [ 1 ] [ i ] );
                  dt := 0;
                  amount := 0;
                  Continue;
               end;

            end;

            reqStatus := True;
            if wordie [ 3 ] = '1'
            then
            else
               if wordie [ 3 ] = '2'
               then amount := -amount
               else
                  if wordie [ 3 ] = '10'
                  then reqStatus := False
                  else
                     if wordie [ 3 ] = '11'
                     then begin
                        amount := -amount;
                        reqStatus := False;
                     end;

            if ReqStatus and ( amount = 0 )
            then Continue;

            ActionTransactions.Add( OneTransaction );

         end;
      end;
   end;
   Revise [ 1 ].Clear; {Удаляем дубликаты платежей - не знаю зачем, просто так. Их не должно быть и так}
   ActionTransactions.Sort ( TransactionsActIdComparer );
   j := 0;
   while True do  begin
      for i := j to ActionTransactions.Count - 2 do
         if  ( ActionTransactions [ i ].actId  = ActionTransactions [ i + 1 ].actId  )
         and ( ActionTransactions [ i ].amount = ActionTransactions [ i + 1 ].amount )
         and ( ActionTransactions [ i ].ptCode = ActionTransactions [ i + 1 ].ptCode )
         then begin
            ActionTransactions.Delete( i );
            j := i;
            Break;
         end;
      if i >= ActionTransactions.Count - 2 then Break;
   end;
   WriteToGlobalLog ('Загружены транзакции БД');

   { ---===<*> Загружаем OFD <*>===--- }

   j := 1;
   if      FileExists (     ofdCSVfile )
   then j := parseOFD ( 0 , ofdCSVfile )
   else
      if      FileExists (     'ofd.csv' )
      then j := parseOFD ( 0 , 'ofd.csv' )
      else
         if FileExists ( 'ofd1.csv' )
         then
            for j := 0 to 99 do
               if   FileExists (     'ofd' + j.ToString + '.csv' )
               then parseOFD   ( j , 'ofd' + j.ToString + '.csv' )
               else
         else begin
            Revise    [ 3 ].Add('Не найдена выгрузка из ОФД');
            if Revise [ 3 ].Count > 0 then Revise [ 3 ].SaveToFile('revise.txt');
            Exit;
         end;

   { ---===<*> Загружаем исключения из OFD <*>===--- }
   ExcludedFiscalTransactions.Clear;
   if FileExists('ofd-exclude.csv')
   then begin
      Revise [ 1 ].LoadFromFile('ofd-exclude.csv');
      for i := 0 to Revise [ 1 ].Count - 1 do  begin
         wordie := Revise [ 1 ] [ i ].Split(['"',';'],TStringSplitOptions.ExcludeEmpty);
         if Length ( wordie ) > 2 then begin
            clearTerminalTransaction( OneTransaction );
            with OneTransaction do  begin

               try

                  log := Revise [ 1 ] [ i ];
                  dt := strToDateTime ( wordie [ 0 ] );
                  fpd := wordie [ 1 ].Trim;
                  if wordie [ 2 ].StartsWith('Приход')
                  then amount := Round ( wordie [ 3 ].ToSingle * 100 )
                  else
                     if wordie [ 2 ].StartsWith('Возврат')
                     then amount := -Round ( wordie [ 3 ].ToSingle * 100 )
                     else Continue;
                  ReqType := ofd;
                  ReqStatus := amount <> 0;
                  if ReqStatus
                  then
                     if wordie [ 2 ].Contains('нал')
                     then ptCode := CASH
                     else
                        if wordie [ 2 ].Contains('эле')
                        then ptCode := BANKCARD
                        else ReqStatus := False;
                  if ReqStatus and ( amount = 0 )
                  then ReqStatus := False;
                  if ReqStatus
                  then ExcludedFiscalTransactions.Add( OneTransaction )

               except
                  ReqStatus := False;
                  Revise [ 2 ].Add( Revise [ 1 ] [ i ] );
               end;

            end;
         end;
      end;
      Revise [ 1 ].Clear;
      m := 0;
      n := 0;
      for i := 0 to ExcludedFiscalTransactions.Count - 1 do
         with ExcludedFiscalTransactions [ i ] do
            case ptCode of
               CASH: m := m + amount;
               BANKCARD: n := n + amount;
            end;
      if ( m <> 0 ) or ( n <> 0 )
      then begin
         Revise [ 3 ].Add('Расхождение в файле исключённых чеков ОФД: наличными ' + FormatFloat ( '#########.00' , m / 100 ) + ', по карте ' + FormatFloat ( '#########.00' , n / 100 ) );
         if Revise [ 3 ].Count > 0 then Revise [ 3 ].SaveToFile('revise.txt');
         Exit;
      end;

      {удаляем исключения OFD}
      i := 0;
      j := 0;
      while i < FiscalTransactions.Count do  begin
         for i := j to FiscalTransactions.Count - 1 do  begin

            foundNearby := False;
            for k := 0 to ExcludedFiscalTransactions.Count - 1 do  begin
               if ExcludedFiscalTransactions [ k ].fpd = FiscalTransactions [ i ].fpd
               then begin
                  ExcludedFiscalTransactions.Delete( k );
                  FiscalTransactions.Delete( i );
                  foundNearby := True;
                  j := i;
                  Break;
               end;
            end;
            if foundNearby then Break;

         end;{for}
      end;{while}

   end;
   WriteToGlobalLog ('Загружены чеки ОФД');

   q := 0;
   Revise [ 1 ].Clear;

   reviseString := 'Чтение ';

   { ---===<*> Загружаем BILLS <*>===--- }
   if FileExists('bills.csv')
   then begin
      Revise [ 1 ].LoadFromFile('bills.csv');

      {2017-06-02,13:41:52.641,=+100+100+100+100+100+100}
      for i := 0 to Revise [ 1 ].Count - 1 do  begin
         wordie := Revise [ 1 ] [ i ].Split([',','.','+','-','='],TStringSplitOptions.ExcludeEmpty);
         if Length ( wordie ) > 4
         then begin
            clearTerminalTransaction( OneTransaction );
            with OneTransaction do  begin

               try

                  log := log + Revise [ 1 ] [ i ] + '     ';
                  dt := strToDateTime ( wordie [ 2 ] + '.' + wordie [ 1 ] + '.' + wordie [ 0 ] + ' ' + wordie [ 3 ] + ',' + wordie [ 4 ] );
                  ptCode := CASH;
                  ReqType := bill;
                  amount := 0;
                  for j := 5 to Length ( wordie ) - 1 do  begin
                     oneAmount := wordie [ j ].ToInteger * 100;
                     for OnePaperValue := Low ( TPaperValue ) to High ( TPaperValue ) do
                        if oneAmount = PaperAmounts [ OnePaperValue ]
                        then inc ( Papers [ OnePaperValue ] );
                     amount := amount + oneAmount;
                  end;
                  ReqStatus := amount > 0;
                  if ReqStatus
                  then BillTransactions.Add( OneTransaction )
                  else Revise [ 2 ].Add( Revise [ 1 ] [ i ] );

               except
                  ReqStatus := False;
                  Revise [ 2 ].Add( Revise [ 1 ] [ i ] );
               end;

            end;
         end;
      end;
      q := Revise [ 1 ].Count;
      Revise [ 1 ].Clear;
   end;

   { ---===<*> Загружаем логи терминала <*>===--- }
{}   if FileExists('logs.csv')
{}   then begin

{}      assignFile ( f , 'logs.csv' );
{}      reset ( f );
{}      while not Eof ( f ) do  begin

{}         clearTerminalTransaction( OneTransaction );
{}         inc ( q );
{}         ReadLn ( f , u );
{}         s := UTF8ToString ( u );
{}         s := s.Substring( s.IndexOf(':') + 1 , s.Length - s.IndexOf(':') - 1 );
{          2017-12-21 08:02:42.448}
{          0123 56 89 12 45 78 012}
{         0          1         2  }
{}         OneTransaction.log := s;
{}         try
{}            OneTransaction.dt := EncodeDateTime(
{}               s.Substring(  0, 4).ToInteger,
{}               s.Substring(  5, 2).ToInteger,
{}               s.Substring(  8, 2).ToInteger,
{}               s.Substring( 11, 2).ToInteger,
{}               s.Substring( 14, 2).ToInteger,
{}               s.Substring( 17, 2).ToInteger,
{}               s.Substring( 20, 3).ToInteger);
{}         except
{}            Continue;
{}         end;
{}         if s.Contains('createPayment')
{}         then begin

      wordie := s.Split(['.',':',' ','"','\',',','-'],TStringSplitOptions.ExcludeEmpty);
      if Length ( wordie ) > 6
      then begin
         with OneTransaction do  begin

            if dt <> 0 then dt := IncSecond ( dt , -1 ); {начало платежа всегда раньше одновременных операций по платежу}

            for j := 9 to Length ( wordie ) - 1 do
               if wordie [ j ] = 'CASH'
               then ptCode := CASH
               else
                  if wordie [ j ] = 'BANKCARD'
                  then ptCode := BANKCARD
                  else
                     if wordie [ j ] = 'trId'
                     then trId := wordie [ j + 1 ]
                     else
                        if wordie [ j ] = 'trActId'
                        then actId := wordie [ j + 1 ];

            ReqType := createPayment;
            ReqStatus := ( actId <> '' ) and ( trId <> '' ) and ( dt <> 0 );
            if ReqStatus
            then BillTransactions.Add( OneTransaction )
            else Revise [ 2 ].Add( 'createPayment:   ' + s );
         end;
      end;


{}         end;
{}         {else}


{}         if ( s.Contains('cardAmount') ) and ( not s.Contains('callback') )
{}         then begin

      foundNearby := False;
      for i := 2017 to CurrentYear do
         if s.Contains('QDateTime(' + i.ToString + '-')
         then begin
            foundNearby := True;
            Break;
         end;
      if foundNearby then Continue;

      wordie := s.Split(['.',':',' ','"','\',',','-','(',')','{','}'],TStringSplitOptions.ExcludeEmpty);
      if Length ( wordie ) > 10
      then begin
         with OneTransaction do  begin
            ReqType := bill;
            ptCode := BANKCARD;
            amount := 0;
            for j := 9 to Length ( wordie ) - 1 do
               if ( wordie [ j ] = 'espp' ) or ( wordie [ j ] = 'esppId' )
               then actId := wordie [ j + 1 ]
               else
                  if wordie [ j ] = 'svcNum'
                  then svcNum := wordie [ j + 1 ]
                  else
                     if wordie [ j ] = 'cardAmount'
                     then
                        try
                           amount := Round ( wordie [ j + 1 ].ToSingle )
                        except
                           try
                              amount := Round ( wordie [ j + 3 ].ToSingle );
                           except
                              amount := 0;
                           end;
                        end
                     else
                        if wordie [ j ] = 'fpd'
                        then
                           try
                              if wordie [ j + 1 ].ToSingle <> 0
                              then fpd := wordie [ j + 1 ]
                           except

                              try
                                 if wordie [ j + 3 ].ToSingle <> 0
                                 then fpd := wordie [ j + 3 ];
                              except
                                 fpd := '';
                              end;

                           end;

            ReqStatus := amount > 0;
            if ReqStatus
            then begin
               BillTransactions.Add( OneTransaction );
               if (  actId <> '' ) and ( ptCode = BANKCARD )
               then BillTransactions2.Add( OneTransaction );
            end
            else Revise [ 2 ].Add( 'cardAmount   ' + s );
         end;
      end;


{}         end;

{}         {else}

{}         if s.Contains('receiptNumber')
{}         then begin

      wordie := s.Split(['.',':',' ','"','\',',','-','(',')','{','}'],TStringSplitOptions.ExcludeEmpty);
      if Length ( wordie ) > 7
      then begin
         with OneTransaction do  begin

            serialNumber := 0;
            throughNumber := 0;
            try
               for j := 9 to Length ( wordie ) - 1 do
                  if wordie [ j ] = 'receiptNumber'
                  then
                     try
                        serialNumber := Round ( wordie [ j + 1 ].ToSingle )
                     except
                        serialNumber := Round ( wordie [ j + 3 ].ToSingle )
                     end
                  else
                     if wordie [ j ] = 'troughNumber'
                     then
                        try
                           throughNumber := Round ( wordie [ j + 1 ].ToSingle )
                        except
                           throughNumber := Round ( wordie [ j + 3 ].ToSingle );
                        end
                     else
                           if wordie [ j ] = 'fpd'
                           then
                              try
                                 if wordie [ j + 1 ].ToSingle <> 0
                                 then fpd := wordie [ j + 1 ]
                              except

                                 try
                                    if wordie [ j + 3 ].ToSingle <> 0
                                    then fpd := wordie [ j + 3 ];
                                 except
                                    fpd := '';
                                 end;

                              end
                           else
                              if ( wordie [ j ] = 'espp' ) or ( wordie [ j ] = 'esppId' )
                              then actId := wordie [ j + 1 ]
                              else
                                 if wordie [ j ] = 'amount'
                                 then
                                    try
                                       amount := Round ( wordie [ j + 1 ].ToSingle )
                                    except
                                       try
                                          amount := Round ( wordie [ j + 3 ].ToSingle );
                                       except
                                          amount := 0;
                                       end;
                                    end
                                 else
                                    if wordie [ j ] = 'bills'
                                    then
                                       for k := 1 to Ord ( High ( TPaperValue ) ) do  begin
                                          { k 1  j+1 купюра j+2 количество
                                            k 2  j+3 купюра j+4 количество
                                            k n  j+2k-1 купюра j+2k количество }
                                          val ( wordie [ j + 2*k-1 ] , OneAmount , i0 );
                                          if i0 = 0
                                          then
                                             for OnePaperValue := Low ( TPaperValue ) to High ( TPaperValue ) do
                                                if OneAmount = PaperAmounts [ OnePaperValue ]
                                                then Break
                                                else
                                          else
                                             Break;
                                          val ( wordie [ j + 2*k ] , i1 , i0 );
                                          if i0 = 0
                                          then
                                             Papers [ OnePaperValue ] := i1
                                          else
                                             Break;
                                       end;
            except
               serialNumber := 0;
               throughNumber := 0;
            end;

            ReqType := cheque;
            ReqStatus := ( serialNumber > 0 ) and ( throughNumber > 0 );
            if ReqStatus
            then begin
               BillTransactions.Add( OneTransaction );
               if actId <> '' then BillTransactions2.Add( OneTransaction );
            end;
         end;
      end;

{}         end;
{}         {else}
{}         if s.Contains('PayTransactionData')
{}         then begin

      wordie := s.Split(['"'],TStringSplitOptions.ExcludeEmpty);
      if Length ( wordie ) > 7
      then begin
         with OneTransaction do  begin
            try
               serialNumber  := wordie [ 1 ].ToInteger;
               throughNumber := wordie [ 5 ].ToInteger;
               ReqType   :=   cheque;
               ReqStatus := ( serialNumber > 0 ) and ( throughNumber > 0 );
            except
               ReqStatus := False;
            end;
            if ReqStatus
            then begin
               BillTransactions.Add( OneTransaction );
               if actId <> '' then BillTransactions2.Add( OneTransaction );
            end
            else Revise [ 2 ].Add( 'PayTransactionData   ' + s );
         end;
      end;

{}         end;
{}         {else}
{}         if s.Contains('callback') or s.Contains('payTransaction')
{}         then begin

      wordie := s.Split(['"','\',':',',','{','}'],TStringSplitOptions.ExcludeEmpty);
      with OneTransaction do  begin
         for j := 1 to Length ( wordie ) - 1 do  begin
            if wordie [ j ] = 'amount'
            then amount := wordie [ j + 1 ].ToInteger
            else
               if wordie [ j ] = 'bills'
               then
                  for k := 1 to Ord ( High ( TPaperValue ) ) do  begin
                     try
                        OneAmount := wordie [ j + 2*k-1 ].ToInteger;
                        for OnePaperValue := Low ( TPaperValue ) to High ( TPaperValue ) do
                           if OneAmount = PaperAmounts [ OnePaperValue ]
                           then
                              try
                                 Papers [ OnePaperValue ] := wordie [ j + 2*k ].ToInteger;
                                 ptCode := CASH;
                                 Break;
                              except
                                 Break;
                              end;
                     except
                        Break;
                     end;
                  end
               else
                  if wordie [ j ] = 'cardAmount'
                  then begin
                     try
                        oneAmount := wordie [ j + 1 ].ToInteger;
                        if oneAmount <> 0
                        then begin
                           amount := oneAmount;
                           ptCode := BANKCARD;
                        end;
                     except
                        Continue;
                     end;
                  end
                  else
                     if wordie [ j ] = 'esppId'
                     then actId := wordie [ j + 1 ]
                     else
                        if wordie [ j ] = 'fpd'
                        then fpd := wordie [ j + 1 ]
                        else
                           if wordie [ j ] = 'receiptNumber'
                           then val ( wordie [ j + 1 ], serialNumber, i0 )
                           else
                              if wordie [ j ] = 'troughNumber'
                              then val ( wordie [ j + 1 ], throughNumber, i0 )
                              else
                                 if wordie [ j ] = 'svcNum'
                                 then svcNum := wordie [ j + 1 ];{нет в callback, есть в payTransaction}
         end;
         ReqType := payTransaction;
         ReqStatus := ( dt <> 0 ) and ( amount <> 0 );
         if ReqStatus
         then begin
            BillTransactions.Add( OneTransaction );
            if actId <> ''
            then BillTransactions2.Add( OneTransaction );
         end;
      end;

{}         end;
{}         {else}
{}         bp.parseBills( s );
{}         if bp.ReqStatus
{}         then begin

      OneTransaction.ReqStatus := bp.ReqStatus;
      OneTransaction.ReqType := bp.ReqType;
      if bp.ReqType = bill
      then begin
         OneTransaction.papers [ bp.paper ] := OneTransaction.papers [ bp.paper ] + 1;
         OneTransaction.amount := PaperAmounts [ bp.paper ];
      end;
      OneTransaction.ptCode := CASH;
      BillTransactions.Add( OneTransaction );

{}         end;

{}      end;
{}      closeFile ( f );

{}   end
{}   else begin
{}      Revise [ 3 ].Add('logs.csv не найден');
        if Revise [ 3 ].Count > 0 then Revise [ 3 ].SaveToFile('revise.txt');
{}      Exit;
{}   end;

   reviseString := reviseString + 'данных';

   if Revise [ 2 ].Count > 0 then Revise [ 2 ].SaveToFile('Parsing errors.txt');
   Revise [ 2 ].Clear;

   WriteToGlobalLog ('Загружены логи терминала');

   {Удаляем дубликаты записей о чеках - в логах их может быть много :}
   {Сортируем по порядку}
   BillTransactions.Sort ( TransactionsThroughNumberComparer );
   {находим начало области поиска}
   for k := 0 to BillTransactions.Count - 1 do
      if BillTransactions [ k ].throughNumber > 0
      then begin
         {выполняем поиск}
         j := k;
         while True do  begin
            for i := j to BillTransactions.Count - 2 do begin
               if  ( BillTransactions [ i ].throughNumber = BillTransactions [ i + 1 ].throughNumber )
               then begin
                  if  ( BillTransactions [ i ].ReqType = payTransaction )
                  and ( BillTransactions [ j ].ReqType = payTransaction )
                  then
                     if Length ( BillTransactions [ i ].log ) >= Length ( BillTransactions [ j ].log )
                     then BillTransactions.Delete( j )
                     else BillTransactions.Delete( i )
                  else
                     if BillTransactions [ i ].ReqType = payTransaction
                     then BillTransactions.Delete ( j )
                     else BillTransactions.Delete ( i );
                  j := i;
                  Break;
               end;
            end;
            if i >= BillTransactions.Count - 2 then Break;
         end;
         {завершаем поиск}
         Break;
      end;
   WriteToGlobalLog ('Удалены дублирующие записи логов');

   reviseString := reviseString + ':';
   Revise [ 3 ].Add( reviseString );
{удаляем  новые платежи без логов терминала - их анализ невозможен}
   i := 0;
   while i < ActionTransactions.Count - 1 do
      if ActionTransactions [ i ].ReqStatus
      then inc ( i )
      else begin
         foundNearby := False;
         for j := 0 to BillTransactions.Count - 1 do
            if ( BillTransactions [ j ].ReqType = createPayment ) and ( ActionTransactions [ i ].actId = BillTransactions [ j ].actId )
            then begin
               foundNearby := True;
               Break;
            end;
         if foundNearby
         then inc ( i )
         else ActionTransactions.Delete( i );
      end;
   WriteToGlobalLog ('Удалены новые платежи без логов');

{сортируем все транзакции по времени}
   ActionTransactions.Sort (TransactionsTimeComparer);
   FiscalTransactions.Sort (TransactionsTimeComparer);
   BillTransactions2.Sort  (TransactionsTimeComparer);
   BillTransactions.Sort   (TransactionsTimeComparer);
   WriteToGlobalLog ('Все транзакции отсортированы по времени');

   Revise [ 3 ].Add('логов терминала ' + BillTransactions.Count.ToString + ' (' + q.ToString  + ')' );
   Revise [ 3 ].Add('транзакций ЕСПП ' + ActionTransactions.Count.ToString );
   Revise [ 3 ].Add('     чеков  ОФД ' + FiscalTransactions.Count.ToString );

   Revise [ 3 ].Add('');
   reviseString := 'Сопоставление транзакций';{первый проход}
   dt1 := Now;

   l := 0;
   while ( ActionTransactions.Count > 0 ) or ( BillTransactions.Count > 0 ) or ( FiscalTransactions.Count > 0 ) do  begin

      clearPayment ( OnePayment );

      {собираем данные терминала}
      if BillTransactions.Count > 0 then begin

         i0 := -1;
         i1 := -1;
         for i := 0 to BillTransactions.Count - 1 do {находим первый createPayment}
            if i0 = -1
            then
               if BillTransactions [ i ].ReqType = createPayment
               then i0 := i
               else
            else
               if i1 = -1
               then
                  if BillTransactions [ i ].ReqType = createPayment
                  then i1 := i
                  else
               else Break; { все данные о платеже - от i0 до i1-1 }

         if i0 = -1 {если нет createPayment - то удаляем все записи}
         then BillTransactions.Clear
         else begin
            if i1 = -1 {если есть только один createPayment, то читаем и удаляем все записи}
            then i1 := BillTransactions.Count;
            {читаем с i0 до i1, удаляем с i0 до i1}
            s := '';
            for i := i0 to i1 - 1 do begin
               case BillTransactions [ i ].ReqType of
                  createPayment:
                     OnePayment [ BillTransactions [ i ].ReqType ] := BillTransactions [ i ];
                  cheque:
                     if ( BillTransactions [ i ].actId = '' )
                     or ( BillTransactions [ i ].actId = OnePayment [ createPayment ].actId )
                     then OnePayment [ BillTransactions [ i ].ReqType ] := BillTransactions [ i ];
                  bill, billError:
                     case BillTransactions [ i ].ptCode of
                        CASH:
{                           if MinutesBetween ( OnePayment [ createPayment ].dt , BillTransactions [ i ].dt ) < 6
                           then }begin
                              OnePayment [ BillTransactions [ i ].ReqType ].dt := BillTransactions [ i ].dt;
                              OnePayment [ BillTransactions [ i ].ReqType ].amount := OnePayment [ BillTransactions [ i ].ReqType ].amount + BillTransactions [ i ].amount;
                              OnePayment [ BillTransactions [ i ].ReqType ].ptCode := CASH;
                              OnePayment [ BillTransactions [ i ].ReqType ].ReqType := bill;
                              if BillTransactions [ i ].ReqType = bill then OnePayment [ BillTransactions [ i ].ReqType ].ReqStatus := BillTransactions [ i ].ReqStatus;
                              for OnePaperValue := Low ( TPaperValue ) to High ( TPaperValue ) do  OnePayment [ BillTransactions [ i ].ReqType ].papers [ OnePaperValue ] := OnePayment [ BillTransactions [ i ].ReqType ].papers [ OnePaperValue ] + BillTransactions [ i ].papers [ OnePaperValue ];
                              s := s + #13 + BillTransactions [ i ].log;
                           end;

                        BANKCARD:
                           if ( not OnePayment [ createPayment ].ReqStatus )
                           or (     OnePayment [ createPayment ].actId = BillTransactions [ i ].actId )
                           or (                                          BillTransactions [ i ].actId = '' )
                           then
{                              if MinutesBetween ( OnePayment [ createPayment ].dt , BillTransactions [ i ].dt ) < 3
                              then }begin
                                 OnePayment [ BillTransactions [ i ].ReqType ] := BillTransactions [ i ];
                                 s := s + BillTransactions [ i ].log + '     ';
                              end;
                     end;

                  payTransaction:
                     if BillTransactions [ i ].actId = OnePayment [ createPayment ].actId
                     then
                        OnePayment [ BillTransactions [ i ].ReqType ] := BillTransactions [ i ];

               end;
            end;
            OnePayment [ bill ].log := s;

            for i := i0 to i1 - 1 do
               if ( OnePayment [ createPayment ].actId = BillTransactions [ i0 ].actId )
               or ( BillTransactions [ i0 ].actId = '' )
               then BillTransactions.Delete( i0 );
         end;

      end;{собираем данные терминала}

      with OnePayment [ createPayment ] do
         if dt <>0 then dt := IncSecond ( dt , 1 ); {возвращаем правильное время запроса}

      {находим платёж в ЕСПП}
      if ActionTransactions.Count > 0 then
         if OnePayment [ createPayment ].dt = 0
         then begin
            OnePayment [ espp ] := ActionTransactions [ 0 ];
            ActionTransactions.Delete( 0 );
         end
         else
            for i := 0 to ActionTransactions.Count - 1 do
               if OnePayment [ createPayment ].actId = ActionTransactions [ i ].actId
               then begin
                  OnePayment [ espp ] := ActionTransactions [ i ];
                  ActionTransactions.Delete( i );
                  Break;
               end;{находим платёж в ЕСПП}
      if ( not OnePayment [ espp].ReqStatus ) and ( OnePayment [ createPayment ].ReqStatus )
      then OnePayment [ espp ].ptCode := OnePayment [ createPayment ].ptCode;

      {Сопоставляем чеки ОФД}
      if  ( not OnePayment[espp].ReqStatus )
      and ( not OnePayment[payTransaction].ReqStatus )
      and ( not OnePayment[bill].ReqStatus )
      and ( OnePayment[createPayment].ReqStatus )
      then
      {1. Есть отдельные запросы, но неизвестна сумма платежа.
            В такие незавершённые платежи чек ОФД добавлен быть не может
            - ОФД

            - ЕСПП
            - payTransaction
            ? receiptNumber
            - bills/cardAmount
            + createPayment }
      else
         if FiscalTransactions.Count > 0
         then begin
            {if onePayment[payTransaction].fpd = ''
            then begin}
               oneAmount := 0;
               if OnePayment[espp].ReqStatus
               then begin
                  onePaymentType := OnePayment[espp].ptCode;
                  oneAmount := OnePayment[espp].amount;
                  dtOne := OnePayment[espp].dt;
               end
               else begin
                  onePaymentType := OnePayment[createPayment].ptCode;
                  if OnePayment[bill].ReqStatus
                  then begin
                     onePaymentType := OnePayment[bill].ptCode;
                     oneAmount := OnePayment[bill].amount;
                     dtOne := OnePayment[bill].dt;
                  end
                  else
                     if OnePayment[payTransaction].ReqStatus
                     then begin
                        oneAmount := OnePayment[payTransaction].amount;
                        dtOne := OnePayment[payTransaction].dt;
                     end
               end;

               if ( oneAmount = 0 )
               then begin
                  OnePayment[ofd] := FiscalTransactions [ 0 ];
                  FiscalTransactions.Delete( 0 );
                  OnePayment[espp].ptCode := OnePayment[ ofd ].ptCode;
               end
               else
                  for j := 0 to FiscalTransactions.Count - 1 do
                     if ( oneAmount = FiscalTransactions [ j ].amount )
                     and ( onePaymentType = FiscalTransactions [ j ].ptCode )
                     and ( DaysBetween( dtOne , FiscalTransactions [ j ].dt ) < 2 )
                     and ( CompareDateTime ( dtOne , FiscalTransactions [ j ].dt ) < 1 )
                     then begin
                        OnePayment[ofd] := FiscalTransactions [ j ];
                        FiscalTransactions.Delete( j );
                        Break;
                     end;
            {end}
            {else
               for j := 0 to FiscalTransactions.Count - 1 do
                  if OnePayment [ payTransaction ].fpd = FiscalTransactions [ j ].fpd
                  then begin
                     OnePayment [ ofd ] := FiscalTransactions [ j ];
                     FiscalTransactions.Delete( j );
                     Break;
                  end;}
         end;{находим в ОФД сумму платежа}

      {заполняем параметры платежа }
      if OnePayment [ espp ].dt = 0 then OnePayment [ espp ].dt := OnePayment [ ofd ].dt;
      if OnePayment [ espp ].dt = 0 then OnePayment [ espp ].dt := OnePayment [ createPayment ].dt;

      {сортируем}
      if  ( not OnePayment [ ofd  ].ReqStatus )
      and ( not OnePayment [ espp ].ReqStatus )
      and ( not OnePayment [ bill ].ReqStatus )
      and ( OnePayment [ payTransaction ].amount = 0 )
      then inc ( l )
      else
         if  ( not ( OnePayment [ espp           ].ReqStatus ) )
         and ( not ( OnePayment [ payTransaction ].ReqStatus ) )
         and ( not ( OnePayment [ cheque         ].ReqStatus ) )
         and ( not ( OnePayment [ bill           ].ReqStatus ) )
         then
            if       OnePayment [ ofd            ].ReqStatus
            then DoublePayments.Add( OnePayment )
            else inc ( l )
         else
            if OnePayment [ espp ].ReqStatus and OnePayment [ ofd ].ReqStatus
            then SuccessfulPayments.Add( OnePayment )
            else
               GeneralPayments.Add( OnePayment );

   end; {while - Сопоставляем транзакции }

   WriteToGlobalLog ('Первый проход сопоставления транзакций завершён (от createPayment до createPayment)');
   dt2 := Now;

   p := l;
   reviseString := reviseString + '   завершено';
   Revise [ 3 ].Add( reviseString );
   Revise [ 3 ].Add('');

   if quick and ( MinutesBetween ( dt1 , dt2 ) > 5 )
   then begin
         Revise [ 3 ].Add('По предварительным данным:');
         Revise [ 3 ].Add( SuccessfulPayments.Count.ToString + ' успешных платежей с чеками' );
         Revise [ 3 ].Add( DoublePayments.Count.ToString + ' чеков без платежей' );
         Revise [ 3 ].Add( GeneralPayments.Count.ToString + ' оплат не сопоставлено зачислениям после первого прохода');
         Revise [ 3 ].Add( '' );
         Revise [ 3 ].Add('Быстрый режим - превышено время выполнения первого прохода');
      if Revise [ 3 ].Count > 0 then Revise [ 3 ].SaveToFile ( 'revise.txt' );
      Exit;
   end;

   {второй проход}
   for i := 0 to BillTransactions2.Count - 1 do
      for j := 0 to GeneralPayments.Count - 1 do
         if ( BillTransactions2 [ i ].actId = GeneralPayments [ j ] [ espp ].actId )
         or ( BillTransactions2 [ i ].actId = GeneralPayments [ j ] [ createPayment ].actId )
         then begin
            OnePayment := GeneralPayments [ j ];
            OnePayment [ BillTransactions2 [ i ].ReqType ] := BillTransactions2 [ i ];
            GeneralPayments [ j ] := OnePayment;
         end;
   BillTransactions2.Clear;
   WriteToGlobalLog ('Второй проход сопоставления транзакций завершён (по actId)');

   reviseString := 'Учёт возвратов платежей';
   Revise [ 1 ].Clear;
   l := 0;
   i := 0;
   while i < GeneralPayments.Count - 1 do  begin
      if      ( GeneralPayments [ i ] [ espp ].ReqStatus )
      and ( not GeneralPayments [ i ] [ ofd  ].ReqStatus )
      then begin
         k := 0;
         for j := i + 1 to GeneralPayments.Count - 1 do
            if ( GeneralPayments [ j ] [ espp ].ReqStatus )
            and ( not GeneralPayments [ j ] [ ofd  ].ReqStatus )
            and ( GeneralPayments [ i ] [ espp ].trId = GeneralPayments [ j ] [ espp ].trId )
            and ( GeneralPayments [ i ] [ espp ].amount + GeneralPayments [ j ] [ espp ].amount = 0 )
            then begin
               k := j;
               Break;
            end;
         if k = 0
         then inc ( i )
         else begin
            if ( DaysBetween( Today , GeneralPayments [ i ] [ espp ].dt ) <= span )
            or ( DaysBetween( Today , GeneralPayments [ i ] [ ofd  ].dt ) <= span )
            or ( DaysBetween( Today , GeneralPayments [ k ] [ espp ].dt ) <= span )
            or ( DaysBetween( Today , GeneralPayments [ k ] [ ofd  ].dt ) <= span )
            then begin
               Revise [ 1 ].Add('ВЫПОЛНЕН ВОЗВРАТ');
               if GeneralPayments [ i ] [ espp ].amount < 0
               then begin
                  writeGeneralPaymentToFile( i, 1 );
                  Revise [ 1 ].Add('ДЛЯ ПЛАТЕЖА');
                  writeGeneralPaymentToFile( k, 1 );
               end
               else begin
                  writeGeneralPaymentToFile( k, 1 );
                  Revise [ 1 ].Add('ДЛЯ ПЛАТЕЖА');
                  writeGeneralPaymentToFile( i, 1 );
               end;
               Revise [ 1 ].Add('');
               Revise [ 1 ].Add('');
               Revise [ 1 ].Add('');
               inc ( l );
            end;
            GeneralPayments.Delete( k );
            GeneralPayments.Delete( i );
         end;
      end
      else inc ( i );
   end;
   if Revise [ 1 ].Count > 0 then Revise [ 1 ].SaveToFile( 'Returned payments.txt' );
   Revise [ 1 ].Clear;
   if l > 0
   then reviseString := reviseString + '   учтено ' + l.ToString + ' возвратов ' + l.ToString + ' платежей'
   else reviseString := reviseString + '   нет возвратов';
   Revise [ 3 ].Add( reviseString );

   Revise [ 3 ].Add('');
   reviseString := 'Приблизительное сравнение чеков и платежей';
   m := 0;
   n := 0;
   i := 0;
   while ( i < DoublePayments.Count ) and ( GeneralPayments.Count > 0 ) do  begin
      foundNearby := False;
      k := 0;
      l := 100;
      for j := 0 to GeneralPayments.Count - 1 do begin

         if  (     GeneralPayments [ j ] [ espp ].ReqStatus )
         and ( not GeneralPayments [ j ] [  ofd ].ReqStatus )
         and (     DoublePayments  [ i ] [  ofd ].amount = GeneralPayments [ j ] [ espp ].amount )
         and (     DoublePayments  [ i ] [  ofd ].ptCode = GeneralPayments [ j ] [ espp ].ptCode )
         and ( HoursBetween ( DoublePayments [ i ] [ ofd ].dt , GeneralPayments [ j ] [ espp ].dt ) < l )
         and ( CompareDateTime ( DoublePayments [ i ] [ ofd ].dt , GeneralPayments [ j ] [ espp ].dt ) > -1 )
         then
{            if ( GeneralPayments [ j ] [ espp ].fpd = '' ) or ( DoublePayments [ i ] [ ofd ].fpd = GeneralPayments [ j ] [ espp ].fpd )
            then}
            begin
               l := HoursBetween ( DoublePayments [ i ] [ ofd ].dt , GeneralPayments [ j ] [ espp ].dt );
               k := j;
               foundNearby := True;
               Break;
            end;
      end;

      if not foundNearby
      then inc ( i )
      else begin

         clearPayment( OnePayment );

         OnePayment [            ofd ] := DoublePayments  [ i ] [            ofd ];
         OnePayment [           espp ] := GeneralPayments [ k ] [           espp ];
         OnePayment [  createPayment ] := GeneralPayments [ k ] [  createPayment ];
         OnePayment [           bill ] := GeneralPayments [ k ] [           bill ];
         OnePayment [         cheque ] := GeneralPayments [ k ] [         cheque ];
         OnePayment [ payTransaction ] := GeneralPayments [ k ] [ payTransaction ];
         SuccessfulPayments.Add( OnePayment );

         DoublePayments.Delete( i );
         GeneralPayments.Delete( k );

         inc ( m );
         if l > n then n := l;
      end;
   end;
   WriteToGlobalLog ('Третий проход сопоставления транзакций завершён (по времени)');

   {Сопоставление оставшихся расхождений}
   while ( i < DoublePayments.Count ) and ( GeneralPayments.Count > 0 ) do  begin
      k := 0;
      foundNearby := False;
      for j := 0 to GeneralPayments.Count - 1 do  begin

         if  (     GeneralPayments [ j ] [ espp ].ReqStatus )
         and ( not GeneralPayments [ j ] [  ofd ].ReqStatus )
         and (     DoublePayments  [ i ] [  ofd ].amount = GeneralPayments [ j ] [ espp ].amount )
         and (     DoublePayments  [ i ] [  ofd ].ptCode = GeneralPayments [ j ] [ espp ].ptCode )
         then
{            if ( GeneralPayments [ j ] [ espp ].fpd = '' )
            or ( DoublePayments  [ i ] [  ofd ].fpd = GeneralPayments [ j ] [ espp ].fpd )
            then}
            begin
               l := HoursBetween ( DoublePayments [ i ] [ ofd ].dt , GeneralPayments [ j ] [ espp ].dt );
               foundNearby := True;
               k := j;
               Break;
            end;
      end;

      if foundNearby
      then begin
         clearPayment( OnePayment );
         OnePayment [            ofd ] := DoublePayments  [ i ] [            ofd ];
         OnePayment [           espp ] := GeneralPayments [ k ] [           espp ];
         OnePayment [  createPayment ] := GeneralPayments [ k ] [  createPayment ];
         OnePayment [           bill ] := GeneralPayments [ k ] [           bill ];
         OnePayment [         cheque ] := GeneralPayments [ k ] [         cheque ];
         OnePayment [ payTransaction ] := GeneralPayments [ k ] [ payTransaction ];
         SuccessfulPayments.Add( OnePayment );

         if ( DaysBetween ( Today , OnePayment [ espp ].dt ) <= span )
         or ( DaysBetween ( Today , OnePayment [ ofd  ].dt ) <= span )
         then begin
            inc ( m );
            if l > n then n := l;
         end;

         DoublePayments.Delete( i );
         GeneralPayments.Delete( k );

      end
      else inc ( i );
   end;
   if m > 0
   then reviseString := reviseString + '   найдено ' + m.ToString + ' чеков на максимальном отдалении ' + n.ToString + ' часов от своих платежей'
   else reviseString := reviseString + '   завершено';
   Revise [ 3 ].Add( reviseString );

   Revise [ 3 ].Add('');
   reviseString := 'Учёт чеков возврата';
   l := 0;
   i := 0;
   Revise [ 1 ].Clear;
   while i < DoublePayments.Count - 1 do  begin
      k := 0;
      for j := i + 1 to DoublePayments.Count - 1 do
         if DoublePayments [ i ] [ ofd ].amount + DoublePayments [ j ] [ ofd ].amount = 0
         then begin
            k := j;
            Break;
         end;
      if k = 0
      then inc ( i )
      else begin

         if ( DaysBetween ( Today , DoublePayments [ i ] [ ofd ].dt ) <= span )
         or ( DaysBetween ( Today , DoublePayments [ k ] [ ofd ].dt ) <= span )
         then begin
            Revise [ 1 ].Add('НАПЕЧАТАН ЧЕК ВОЗВРАТА');
            if DoublePayments [ i ] [ ofd ].amount < 0
            then begin
               writeDoublePaymentToFile( i, 1 );
               Revise [ 1 ].Add('ДЛЯ ЧЕКА ПРИХОДА');
               writeDoublePaymentToFile( k, 1 );
            end
            else begin
               writeDoublePaymentToFile( k, 1 );
               Revise [ 1 ].Add('ДЛЯ ЧЕКА ПРИХОДА');
               writeDoublePaymentToFile( i, 1 );
            end;
            Revise [ 1 ].Add('');
            Revise [ 1 ].Add('');
            Revise [ 1 ].Add('');
            inc ( l );
         end;

         DoublePayments.Delete( k );
         DoublePayments.Delete( i );
      end;
   end;
   if Revise [ 1 ].Count > 0 then Revise [ 1 ].SaveToFile( 'Returned receipts.txt' );
   Revise [ 1 ].Clear;
   if l > 0
   then reviseString := reviseString + '   учтено ' + l.ToString + ' чеков возврата для ' + l.ToString + ' чеков прихода'
   else reviseString := reviseString + '   нет чеков возврата';
   Revise [ 3 ].Add( reviseString );

   WriteToGlobalLog ('Классификация расхождений завершена');

   j := 0; {ОФД наличные}
   k := 0; {ОФД по карте}
   m := 0; {ЕСПП наличные}
   n := 0; {ЕСПП по карте}
   for i := 0 to DoublePayments.Count - 1 do
      with DoublePayments [ i ] [ ofd ] do
         if ReqStatus
         and ( DaysBetween ( Today , DoublePayments [ i ] [ ofd ].dt ) <= span )
         then
            case ptCode of
               CASH: j := j + amount;
               BANKCARD: k := k + amount;
            end;
   for i := 0 to GeneralPayments.Count - 1 do
      if ( DaysBetween ( Today , GeneralPayments [ i ] [ ofd  ].dt ) <= span )
      or ( DaysBetween ( Today , GeneralPayments [ i ] [ espp ].dt ) <= span )
      then begin
         with GeneralPayments [ i ] [ ofd ] do
            if ReqStatus
            then
               case ptCode of
                  CASH: j := j + amount;
                  BANKCARD: k := k + amount;
               end;
         with GeneralPayments [ i ] [ espp ] do
            if ReqStatus
            then
               case ptCode of
                  CASH:     m := m + amount;
                  BANKCARD: n := n + amount;
               end;
      end;
   i0 := 0;
   for i := 0 to SuccessfulPayments.Count - 1 do
      if ( DaysBetween ( Today , SuccessfulPayments [ i ] [ ofd  ].dt ) <= span )
      or ( DaysBetween ( Today , SuccessfulPayments [ i ] [ espp ].dt ) <= span )
      then begin
         inc ( i0 );
         with SuccessfulPayments [ i ] [ ofd ] do
            if ReqStatus
            then
               case ptCode of
                  CASH: j := j + amount;
                  BANKCARD: k := k + amount;
               end;
         with SuccessfulPayments [ i ] [ espp ] do
            if ReqStatus
            then
               case ptCode of
                  CASH:     m := m + amount;
                  BANKCARD: n := n + amount;
               end;
      end;
   Revise [ 3 ].Add('');

   Revise [ 3 ].Add('Найдено:');
   Revise [ 3 ].Add('Успешных платежей ' + i0.ToString );
   Revise [ 3 ].Add('Невыполненных оплат ' + p.ToString );
   Revise [ 3 ].Add('Не сопоставлено транзакций ' + ( DoublePayments.Count + GeneralPayments.Count ).ToString );
   Revise [ 3 ].Add('');

   Revise [ 3 ].Add( 'Расхождение между ЕСПП и ОФД ' + FormatFloat ( '#########.00' , abs ( m + n - j - k ) / 100 ) + ' рублей, из них ' + FormatFloat ( '#########.00' , abs ( m - j ) / 100 ) + ' рублей наличными и ' + FormatFloat ( '#########.00' , abs ( n - k ) / 100 ) + ' рублей по карте');
   Revise [ 3 ].Add( 'Зачислено абонентам '          + FormatFloat ( '#########.00' ,     ( m + n         ) / 100 ) + ' рублей, из них ' + FormatFloat ( '#########.00' ,       m       / 100 ) + ' рублей наличными и ' + FormatFloat ( '#########.00' ,       n       / 100 ) + ' рублей по карте');
   Revise [ 3 ].Add( 'Отражено в ОФД '               + FormatFloat ( '#########.00' ,     (         j + k ) / 100 ) + ' рублей, из них ' + FormatFloat ( '#########.00' ,           j   / 100 ) + ' рублей наличными и ' + FormatFloat ( '#########.00' ,           k   / 100 ) + ' рублей по карте');
   Revise [ 3 ].Add('');

   if DoublePayments.Count + GeneralPayments.Count > 0
   then begin

      Revise [ 3 ].Add('Анализ расхождений:');

      reviseString := 'Поиск двойных чеков';
      o := 0;
      i := 0;
      while i < DoublePayments.Count do  begin
         for j := 0 to SuccessfulPayments.Count - 1 do  begin
            if  ( DoublePayments [ i ] [ ofd ].amount = SuccessfulPayments [ j ] [ ofd ].amount )
            and ( DoublePayments [ i ] [ ofd ].ptCode = SuccessfulPayments [ j ] [ ofd ].ptCode )
            and ( MinutesBetween ( DoublePayments [ i ] [ ofd ].dt , SuccessfulPayments [ j ] [ ofd ].dt ) < 2 )
            then begin

               if ( DaysBetween ( Today , DoublePayments [ i ] [ ofd ].dt ) <= span )
               or ( DaysBetween ( Today , SuccessfulPayments [ j ] [ ofd ].dt ) <= span )
               then begin
                  Revise [ 3 ].Add('');
                  Revise [ 3 ].Add('');
                  Revise [ 3 ].Add('Лишний чек:');
                  writeDoublePaymentToFile ( i, 3 );
                  Revise [ 3 ].Add('Является лишним чеком платежа');
                  Revise [ 3 ].Add('');
                  writeSuccessfulPaymentToFile ( j, 3 );
                  Revise [ 3 ].Add('');
                  inc ( o );
               end;

               DoublePayments.Delete( i );
               Dec ( i );
               Break;

            end;
         end;
         inc ( i );
      end;

      i := 0;
      while i < DoublePayments.Count do  begin
         for j := 0 to GeneralPayments.Count - 1 do  begin
            if                                        ( GeneralPayments [ j ] [ ofd ].ReqStatus )
            and ( DoublePayments [ i ] [ ofd ].amount = GeneralPayments [ j ] [ ofd ].amount )
            and ( DoublePayments [ i ] [ ofd ].ptCode = GeneralPayments [ j ] [ ofd ].ptCode )
            and ( MinutesBetween ( DoublePayments [ i ] [ ofd ].dt , GeneralPayments [ j ] [ ofd ].dt ) < 2 )
            then begin

               if ( DaysBetween ( Today , DoublePayments [ i ] [ ofd ].dt ) <= span )
               or ( DaysBetween ( Today , GeneralPayments [ j ] [ ofd ].dt ) <= span )
               then begin
                  Revise [ 3 ].Add('');
                  Revise [ 3 ].Add('');
                  Revise [ 3 ].Add('Лишний чек:');
                  writeDoublePaymentToFile ( i , 3 );
                  Revise [ 3 ].Add('Является лишним чеком платежа');
                  Revise [ 3 ].Add('');
                  writeGeneralPaymentToFile ( j , 3 );
                  Revise [ 3 ].Add('');
                  inc ( o );
               end;

               DoublePayments.Delete( i );
               Dec ( i );
               Break;
            end;
         end;
         inc ( i );
      end;
      if o = 0
      then reviseString := reviseString + '   завершён'
      else reviseString := reviseString + '   ' + o.ToString + ' двойных чеков';
      Revise [ 3 ].Add( reviseString );

      if DoublePayments.Count > 0
      then begin
         Revise [ 3 ].Add('');
         Revise [ 3 ].Add('Список лишних чеков');
         Revise [ 3 ].Add('');
      end;
      for i := 0 to DoublePayments.Count - 1 do
         if DaysBetween ( Today , DoublePayments [ i ] [ ofd ].dt ) <= span
         then begin
            writeDoublePaymentToFile ( i , 3 );
            Revise [ 3 ].Add('');
            Revise [ 3 ].Add('');
         end;

      if GeneralPayments.Count > 0
      then begin
         Revise [ 3 ].Add('');
         Revise [ 3 ].Add('Список расхождений для ручного разбора');
         Revise [ 3 ].Add('');
      end;
      for i := 0 to GeneralPayments.Count - 1 do
         if ( DaysBetween ( Today , GeneralPayments [ i ] [ espp ].dt ) <= span )
         or ( DaysBetween ( Today , GeneralPayments [ i ] [ ofd  ].dt ) <= span )
         or ( DaysBetween ( Today , GeneralPayments [ i ] [ createPayment ].dt ) <= span )
         then begin
            writeGeneralPaymentToFile ( i , 3 );
            Revise [ 3 ].Add('');
            Revise [ 3 ].Add('');
            Revise [ 3 ].Add('');
         end;
   end;
   WriteToGlobalLog ('Сопоставление расхождений завершено');

   {Выводим список успешных платежей}
   Revise [ 1 ].Clear;
   for i := 0 to SuccessfulPayments.Count - 1 do
      if DaysBetween ( Today , SuccessfulPayments [ i ] [ espp ].dt ) <= span
      then writeSuccessfulPaymentToFile( i , 1 );
   if Revise [ 1 ].Count > 0 then Revise [ 1 ].SaveToFile('Successful payments.txt');
   Revise [ 1 ].Clear;

   {Сохраняем протокол сверки}
   if Revise [ 3 ].Count > 0 then Revise [ 3 ].SaveToFile('revise.txt');
   Revise [ 3 ].Clear;
   WriteToGlobalLog ('Сохранение результатов сверки чеков и оплат');

   { ---===<*> Ждём нажатия ввода <*>===--- }
   except
      on E: Exception do  begin
         Revise [ 3 ].Add ( E.ClassName + ': ' + E.Message );
         if Revise [ 3 ].Count > 0 then Revise [ 3 ].SaveToFile ( 'revise.txt' );
      end;
   end;
end;

begin
   { ---===<*> Инициализация <*>===--- }
   bp := TBillParser.Create;
   Revise [ 1 ] := TStringList.Create;
   Revise [ 2 ] := TStringList.Create;
   Revise [ 3 ] := TStringList.Create;
   ActionTransactions := TList<TTerminalTransaction>.Create;
   BillTransactions   := TList<TTerminalTransaction>.Create;
   BillTransactions2  := TList<TTerminalTransaction>.Create;
   FiscalTransactions := TList<TTerminalTransaction>.Create;
   ExcludedFiscalTransactions
                      := TList<TTerminalTransaction>.Create;
   SuccessfulPayments := TList<TTerminalPayment>.Create;
   GeneralPayments    := TList<TTerminalPayment>.Create;
   DoublePayments     := TList<TTerminalPayment>.Create;

   TransactionsTimeComparer := TDelegatedComparer<TTerminalTransaction>.Create(
       function( const item1, item2: TTerminalTransaction ): Integer
         begin
            Result := CompareDateTime ( TTerminalTransaction( item1 ).dt, TTerminalTransaction( item2 ).dt );
         end);

   TransactionsActIdComparer := TDelegatedComparer<TTerminalTransaction>.Create(
       function( const item1, item2: TTerminalTransaction ): Integer
         begin
            Result := CompareText ( TTerminalTransaction( item1 ).actId , TTerminalTransaction( item2 ).actId );
         end);

   TransactionsTrIdComparer := TDelegatedComparer<TTerminalTransaction>.Create(
       function( const item1, item2: TTerminalTransaction ): Integer
         begin
            Result := CompareText ( TTerminalTransaction( item1 ).trId , TTerminalTransaction( item2 ).trId );
         end);

   TransactionsThroughNumberComparer := TDelegatedComparer<TTerminalTransaction>.Create(
       function( const item1, item2: TTerminalTransaction ): Integer
         begin
            Result := CompareValue ( TTerminalTransaction( item1 ).throughNumber , TTerminalTransaction( item2 ).throughNumber );
         end);

   PaymentTimeComparer := TDelegatedComparer<TTerminalPayment>.Create(
       function( const item1, item2: TTerminalPayment ): Integer
         begin
            Result := CompareDateTime ( TTerminalPayment(item1)[espp].dt, TTerminalPayment(item2)[espp].dt );
         end);

   PaymentTypeName [CASH    ] := 'наличными';
   PaymentTypeName [BANKCARD] := 'по карте';
end.
